//###############
//FILE : Message.java
//WRITER : Daniel Lowengrub, daniel_i_l, 320687098
//EXERCISE : oop ex3 2011
//DESCRIPTION:
//See below
//###############
package oop.ex3.protocol.messages;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import oop.ex3.exceptions.InvalidMessageException;

/**
 * This is the abstract class that all messages inherit from.
 * It provides functionality which is common to all messages such as sending the header and message
 * end. It's used both for sending a message and receiving it. If it receives the wrong message then it throws
 * an exception.
 * Base classes implement the sendDetails and receiveDetails methods which specifies what the child
 * sends and receives in addition to the headers and ending which are common to everyone.
 * @author daniel
 */
public abstract class Message {
    private static final String _end = "END";
    private static final int LOOK_AHEAD = 100;
    private String _header;

    /**
     * Create a message and initialize it with a certain header message string.
     * @param header The header message.
     */
    public Message (String header) {
        _header = header;
    }

    /**
     * Send this message over the given output stream. It sends the header, then the details then
     * the ending.
     * @param outputStream The output stream to send the data on.
     * @throws IOException If there was a problem sending the data.
     */
    public void send(DataOutputStream outputStream) throws IOException {
        outputStream.writeUTF(_header);
        //System.out.println("sent: " + _header);
        sendDetails(outputStream);
        outputStream.writeUTF(_end);
        outputStream.flush();
    }

    /**
     * The child should put anything he wants to send after the header in this method.
     * @param outputStream The stream to send the data on.
     * @throws IOException If there was a problem sending the data.
     */
    protected abstract void sendDetails(DataOutputStream outputStream) throws IOException;

    /**
     * Check if the given input stream starts with the header of this message. 
     * The stream must support the mark and reset methods.
     * @param inputStream The stream to check.
     * @return True iff the stream starts with the header.
     * @throws IOException If there was a problem reading the stream.
     */
    public boolean matchHeader(DataInputStream inputStream) throws IOException{
        //System.out.println("looking for " + _header);
        inputStream.mark(LOOK_AHEAD);
        String header = inputStream.readUTF(); //we dont need to check the header anymore
        //System.out.println("found " + header);
        inputStream.reset();
        boolean output = _header.equals(header);
        return output;
    }
    
    /**
     * Receive this message from a stream. It first receives the header and throws an exception
     * if the information doesn't match what it's looking for.
     * @param inputStream The input stream to receive from.
     * @throws IOException If there was a problem getting input.
     * @throws InvalidMessageException If the header doesn't match this message header.
     */
    public void receive(DataInputStream inputStream) throws IOException, InvalidMessageException{
        String header = inputStream.readUTF();
        //System.out.println("received: " + header);
        if (!header.equals(_header))
            throw new InvalidMessageException("Incorrect Header : " + header + ". Should be: " + _header);
        
        receiveDetails(inputStream);
        
        String end = inputStream.readUTF();
        if (!end.equals(_end))
            throw new InvalidMessageException("Incorrect End: " + end);
    }

    /**
     * The child should put anything he wants to receive after the header in this method.
     * @param outputStream The stream to receive the data on.
     * @throws IOException If there was a problem receiving the data.
     */
    protected abstract void receiveDetails(DataInputStream inputStream) throws IOException, InvalidMessageException;
}
